import ogre.renderer.OGRE as ogre
import ogre.addons.noise as noise

import copy

import random

def getTangent(idx, t, spline):
     """
     Derivative of the equation #2: http://www.mvps.org/directx/articles/catmull/
     his method assumes that the passed in spline contain S segments with S + 3 control points. Passed in idx should start at 1 and end at N - 2 for some spline with N control points.
     """
     p0 = spline.getPoint(idx - 1)
     p1 = spline.getPoint(idx)
     p2 = spline.getPoint(idx + 1)
     p3 = spline.getPoint(idx + 2)
        
     tangent = 0.5 * ((p2 - p0) + 2.0 * t *(2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) + 3.0 * t ** 2 * (-p0 + 3.0 * p1 - 3.0 * p2 + p3))
        
     return tangent


class SplineTrack:
    def __init__(self, spline, totalTime = 1000.0):
        self.orients = []
        self.spline = spline
        self.totalTime= totalTime
        self.totalDistance = 0.0 #unit is in nodes traveled.
        self.totalOrientsDistance = 0.0
        self.numSegments = self.spline.getNumPoints() - 3
        self.timePerNode = 0.5 #time spent per node
        self.currentOrient = None
        self.currentPos = None
    
    def __reinit(self):
        self.currentPos = ogre.Vector3(0, 0, 0)#self.spline.interpolate(currentNode, 0.0)
        self.currentOrient = self.orients[0]
    def __copy__(self):
        copyTrack = SplineTrack(self.spline)
        copyTrack.orients = self.orients
        copyTrack.__reinit()
        return copyTrack
    
    def setTimeFrame(self, time):
        """
        Sets the time frame over the entire length of segments.
        The parameter time is between 0...1
        """
        if time > 1.0:
            time = 1.0
        if time < 0.0:
            time = 0.0
        self.totalDistance = time * float(self.numSegments)
        self.totalOrientsDistance = self.totalDistance
        self.advance(0.0)
        return

    def begin(self):
        return
    def addOrient(self, orient):
        self.orients.append(orient)
    
    def end(self):
        self.orientPerSecond = float(len(self.orients)) / self.totalTime 
        currentNode = 1
        self.currentPos = ogre.Vector3(0, 0, 0)#self.spline.interpolate(currentNode, 0.0)
        self.currentOrient = self.orients[0]
                
    def getCurrentFrame(self):
        """
        This method will always return a valid position and orient.
        If at the end of the spline path, it will return the last valid position and orient.
        """
        return self.currentPos, self.currentOrient
    
    def hasNext(self, dt):
        newT = self.totalOrientsDistance + dt / self.timePerNode
        if newT > self.numSegments:
            return False
        else:
            return True
    
    def getNextFrame(self, dt):
        hasNext, newPos, newOrient, newT = self._deltaFrame(dt)
        return hasNext, newPos, newOrient
    
    def _deltaFrame(self, dt):
        newT = self.totalOrientsDistance + dt / self.timePerNode
        if newT > self.numSegments or newT < 0:
            return False, None, None, None
        currentNode = int(newT)
        pos = self.spline.interpolate(currentNode, newT - float(currentNode))
        orient = self.orients[int(self.totalOrientsDistance)]
        return True, pos, orient, newT
    
    def advance(self, dt):
        currentNode = int(self.totalDistance)
        hasNext, newPos, newOrient, newT = self._deltaFrame(dt)
        if hasNext:
            self.currentPos = newPos
            self.currentOrient = newOrient
            self.totalDistance = newT
            self.totalOrientsDistance = newT


class PerlinBuildingGen:
    """
    This class will generate buildings for the game. It is assume there is a divider, or empty region between buildings, therefore this class generates building 
    on both sides of the divider region. This class works in local model space with the origin at the bottom-left in the enclosing volume having coordinates strictly increasing with the
    divider to the right of the origin in the middle of the enclosing volume. It is assumed that whoever
    uses this class will take this into account. (It's easier this way because it's easier to tile cubes).
    """
    def __init__(self, pageXY, width=24, length=5000, numOfZSlices=100, numOfXSlices=4, maxHeight=32.0, dividerWidth=24, unitSlice=1.0):
        self.width = width
        self.length = length
        self.diverWidth = dividerWidth
        self.numOfZSlices = numOfZSlices
        self.numOfXSlices = numOfXSlices
        self.maxHeight = maxHeight
        self.perlin = noise.Perlin()
        self.perlin.SetFrequency(2)
        self.perlin.SetOctaveCount(2)
        self.halfNumOfZSlices = numOfZSlices / 2
        self.halfNumOfXSlices = numOfXSlices / 2
        self.unitXSlice = float(width) / float(numOfXSlices)
        self.spline = None
        self.numberPerSegment = 25 #number of interpolation per segment
        
    def getSplineTrack(self):
        
        return copy.copy(self.splineTrack)
            
    def generateSpline(self, pageFrame):
        """
        This method generate spline points by sampling from motion. This is a temporary method for feasibility testing. 
        You need to be aware of the sampling rate of the spline with respect to the motion that it's trying to represent:
        
        To decrease frequency change angular delta value. To increase sampling point change either self.length, or numberOfZSlices over this self.length.
        
        This method will be refactored soon.
        
        """
        spline = ogre.SimpleSpline()
        #angular rates is per Z slice. 
        lengthPerSlice = float(self.length) / self.numOfZSlices
        
        elevateAngular = 4.0
        yawAngular = 2.0
        rollAngular = 0.0
        
        
        numOfPoints = self.numOfZSlices
        dZ = float(self.length) / numOfPoints
        
        
        orient = ogre.Quaternion()
        pt = ogre.Vector3(0.0, 0.0, 0.0)# + pageFrame
        spline.addPoint(ogre.Vector3(0.0, 0.0, 0.0))
        #spline.addPoint(pt)
        for i in range(numOfPoints):
            
            dir = random.choice((-1, 1))
            
            
            dElevate = ogre.Quaternion(ogre.Radian(ogre.Degree(elevateAngular / lengthPerSlice).valueRadians()), ogre.Vector3(1.0, 0.0, 0.0))
            dYaw = ogre.Quaternion(ogre.Radian(ogre.Degree(yawAngular / lengthPerSlice).valueRadians()), ogre.Vector3(0.0, 1.0, 0.0))
            dRoll = ogre.Quaternion(ogre.Radian(ogre.Degree(rollAngular / lengthPerSlice).valueRadians()), ogre.Vector3(0.0, 0.0, 1.0))
            spline.addPoint(pt)
            orient = orient * dElevate * dYaw * dRoll
            pt = pt - orient.zAxis() * dZ
        
        self.spline = spline
    
   
         
        
        
    def generate(self, manMesh):
        manMesh.clear()
        self._generate(manMesh) #generate is in a 'private' function because we may want to do beginUpdate later. The structure of the man meshes ought to remain the same.
        manMesh.end()
    
    def _getRandom(self, pos):
        rand = (self.perlin.GetValue(pos.x, pos.y, pos.z) + 1.0) * 0.5
        
        return rand
    
    def getBasisFromDirection(self, direction, upVector):
        zVec = direction.normalisedCopy()
        xVec = upVector.crossProduct(zVec)
        if xVec.isZeroLength():
            xVec = ogre.Vector3(1.0, 0.0, 0.0)
        xVec.normalise()
        yVec = zVec.crossProduct(xVec)
        yVec.normalise()
        return xVec, yVec, zVec
        
        
    
    def _generateSection(self, manMesh, origin, triIndex, first=True):
        """
        This method generates the current section on the spline. This is a temporary method for feasibility testing. 
        You need to be aware of the sampling rate of the spline with respect to the information that it's trying to represent because:
        a) This method generates mesh based on the tangent at some t on the curve and that tangent only. Tangents can only approximate the curve.
        So for example if you are trying to do very hard turns you should adjust the sampling rate of your spline accordingly.
        """
        normalFront = ogre.Vector3(0, 0, 1)
        normalRight = ogre.Vector3(1, 0, 0)
        normalTop = ogre.Vector3(0, 1, 0)
        normalLeft = ogre.Vector3(-1, 0, 0)
        uvBottomLeft = ogre.Vector2(0.0, 0.0)
        uvBottomRight = ogre.Vector2(1.0, 0.0)
        uvTopRight = ogre.Vector2(1.0, 1.0)
        uvTopLeft = ogre.Vector2(0.0, 1.0)
        topLeftFront = None
        topRightFront = None
        topLeftBack = None
        topRightBack = None
        bottomLeftFront = None
        bottomRightFront = None
        bottomLeftBack = None
        bottomRightBack = None
        
        totalSegments = self.spline.getNumPoints() - 3
        #Use quaternion interpolation for now and maybe switch to parallel transport frame later
        tangent = self.spline.interpolate(0, 0.0)
        #tangent.normalise()
        xVec,yVec,zVec = self.getBasisFromDirection(tangent, ogre.Vector3(0.0, 1.0, 0.0))
        frame = ogre.Quaternion(xVec, yVec, zVec)
        #frame = ogre.Quaternion()
        perSegmentTotal = self.numberPerSegment
        
        unitZSlice = float(self.length) - self.numOfZSlices
        #unit z slice should be unit z segment
        self.unitZSlice =  (float(self.length) - 3.0 * unitZSlice ) / totalSegments
        
        if first:
            self.splineTrack = SplineTrack(self.spline)
            self.splineTrack.begin()
        #origin.y = self.maxHeight * self._getRandom(origin) * 0.25      
        #for j in range(0, self.numOfZSlices):
        initTrue = True
        for j in range(totalSegments):
            for seg in range(perSegmentTotal):
                seg = seg
                #eval curve and construct parallel transport frame.
                dseg = float(seg) / (perSegmentTotal - 1)
                originFrame = self.spline.interpolate(j + 1, dseg);
                #new frame can be found by rotating tangent into new tangent about an axis A. Found in Game Programming Gems 2.
                newTangent = getTangent(j + 1, dseg, self.spline)
                #newTangent.normalise()
                axis = tangent.crossProduct(newTangent)
                deltaQuat = ogre.Quaternion()
                if not axis.isZeroLength():
                    angle = ogre.Math.ACos(tangent.dotProduct(newTangent) / (tangent.length() * newTangent.length()))
                    deltaQuat = ogre.Quaternion(ogre.Radian(angle), axis)
                    frame = frame * deltaQuat
                    
                    #frame = frame * -deltaQuat
                dx = frame.xAxis() * self.unitXSlice
                dy = frame.yAxis() * self.maxHeight
                dz = frame.zAxis() * self.unitZSlice
                
                originFrame.x = originFrame.x + frame.xAxis().x * origin.x #This is to take into account the divider. We're going to refactor how we're generate the two segments soon.
                
                tangent = newTangent
                
                if first:
                    self.splineTrack.addOrient(frame)
                    
                               
                #need to refactor the loop below. 
                for i in range(0, self.halfNumOfXSlices):
                    nthOrigin = originFrame + frame.xAxis() * i * self.unitXSlice# + frame.zAxis() * j * self.unitZSlice 
                    #nthOrigin = originFrame + ogre.Vector3(i * frame.xAxis().x * self.unitXSlice, 0.0, j * frameself.unitZSlice)
                    #dz.y = self.maxHeight * self._getRandom(nthOrigin + dz)
                    topLeftFront = (dy) * self._getRandom(nthOrigin + dy)
                    topRightFront = (dx + dy)* self._getRandom(nthOrigin + dx + dy)
                    topLeftBack = (dy + dz) * self._getRandom(nthOrigin + dy + dz)
                    topRightBack = (dx + dy + dz) * self._getRandom(nthOrigin + dx + dy + dz)
                    
                    bottomLeftFront = dx * self._getRandom(nthOrigin) * 0.75
                    bottomRightFront = dx * self._getRandom(nthOrigin + dx)
                    bottomLeftBack = (dz) * self._getRandom(nthOrigin + dz)
                    bottomRightBack = (dx + dz) * self._getRandom(nthOrigin + dx + dz)
                    
                    #front face
                    #bottom left
                    manMesh.position(nthOrigin + bottomLeftFront)
                    manMesh.normal(normalFront)
                    manMesh.textureCoord(uvBottomLeft)
                    #bottom right
                    manMesh.position(nthOrigin + bottomRightFront)
                    manMesh.normal(normalFront)
                    manMesh.textureCoord(uvBottomRight)
                    #top left
                    manMesh.position(nthOrigin + topLeftFront)
                    manMesh.normal(normalFront)
                    manMesh.textureCoord(uvTopLeft)
                    #top right
                    manMesh.position(nthOrigin + topRightFront)
                    manMesh.normal(normalFront)
                    manMesh.textureCoord(uvTopRight)
                    
                    manMesh.triangle(triIndex, triIndex + 1, triIndex + 2)
                    manMesh.triangle(triIndex + 1, triIndex + 3, triIndex + 2)
                    triIndex += 4
                    
                    
                    #top face
                    #bottom left !!!NORMALS ARE TEMP!!!
                    manMesh.position(nthOrigin + topLeftFront)
                    manMesh.normal(normalTop)
                    manMesh.textureCoord(uvBottomLeft)
                    #bottom right
                    manMesh.position(nthOrigin + topRightFront)
                    manMesh.normal(normalTop)
                    manMesh.textureCoord(uvBottomRight)
                    #top left
                    manMesh.position(nthOrigin + topLeftBack)
                    manMesh.normal(normalTop)
                    manMesh.textureCoord(uvTopLeft)
                    #top right
                    manMesh.position(nthOrigin + topRightBack)
                    manMesh.normal(normalTop)
                    manMesh.textureCoord(uvTopRight)
                    
                    manMesh.triangle(triIndex, triIndex + 1, triIndex + 2)
                    manMesh.triangle(triIndex + 1, triIndex + 3, triIndex + 2)
                    triIndex += 4
                              
                    #right face
                    #bottom left
                    manMesh.position(nthOrigin + bottomRightFront)
                    manMesh.normal(normalRight)
                    manMesh.textureCoord(uvBottomLeft)
                    
                    #bottom right
                    manMesh.position(nthOrigin + bottomRightBack)
                    manMesh.normal(normalRight)
                    manMesh.textureCoord(uvBottomRight)
                    
                    #top left
                    manMesh.position(nthOrigin + topRightFront)
                    manMesh.normal(normalRight)
                    manMesh.textureCoord(uvTopLeft)
                    
                    #topRight
                    manMesh.position(nthOrigin + topRightBack)
                    manMesh.normal(normalRight)
                    manMesh.textureCoord(uvTopRight)
                    
                    manMesh.triangle(triIndex, triIndex + 1, triIndex + 2)
                    manMesh.triangle(triIndex + 1, triIndex + 3, triIndex + 2)
                    triIndex += 4
                    
                    #left face
                    manMesh.position(nthOrigin + bottomLeftBack)
                    manMesh.normal(normalLeft)
                    manMesh.textureCoord(uvBottomLeft)
                    
                    #bottom right
                    manMesh.position(nthOrigin + bottomLeftFront)
                    manMesh.normal(normalLeft)
                    manMesh.textureCoord(uvBottomRight)
                    
                    #top left
                    manMesh.position(nthOrigin + topLeftBack)
                    manMesh.normal(normalLeft)
                    manMesh.textureCoord(uvTopLeft)
                    
                    #top right
                    manMesh.position(nthOrigin + topLeftFront)
                    manMesh.normal(normalLeft)
                    manMesh.textureCoord(uvTopRight)
                    
                    manMesh.triangle(triIndex, triIndex + 1, triIndex + 2)
                    manMesh.triangle(triIndex + 1, triIndex + 3, triIndex + 2)
                    triIndex += 4
        if first:
            self.splineTrack.end()
        return triIndex
            
    def _generate(self, manMesh):
        manMesh.begin("GridNormalMap")
        #preprocess the first z slice
        origin = ogre.Vector3(0, 0, 0)
        triIndex = 0
        triIndex = self._generateSection(manMesh, origin, triIndex)
        origin = ogre.Vector3(float(self.diverWidth) + float(self.width) / 2.0, 0.0, 0.0)
        triIndex = self._generateSection(manMesh, origin, triIndex, False)
        
                
    
            